using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using Mbs.Collections;
using Mbs.Globalization;

namespace Mbs
{
    class DefaultStringParser : IStringParser
    {
        public IDictionary<string, string> Properties { get; private set; }
        readonly Dictionary<string, IStringTagProvider> stringTagProviders;
        readonly Dictionary<string, object> propertyObjects;


        public DefaultStringParser()
        {
            propertyObjects = new Dictionary<string, object>();
            stringTagProviders = new Dictionary<string, IStringTagProvider>();

            Properties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        }

        /// <summary>
        /// Expands ${xyz} style property values.
        /// </summary>
        public string Parse(string input)
        {
            return Parse(input, null);
        }

        public void RegisterTagProvider(IStringTagProvider tagProvider)
        {
            foreach (string str in tagProvider.Tags)
                stringTagProviders[str] = tagProvider;
        }

        void Parse(string[] inputs)
        {
            for (int i = 0; i < inputs.Length; ++i)
            {
                inputs[i] = Parse(inputs[i], null);
            }
        }

        /// <summary>
        /// Expands ${xyz} style property values.
        /// </summary>
        public string Parse(string input, string[,] customTags)
        {
            if (input == null)
                return null;
            int pos = 0;
            StringBuilder output = null; // don't use StringBuilder if input is a single property
            do
            {
                int oldPos = pos;
                pos = input.IndexOf("${", pos, StringComparison.Ordinal);
                if (pos < 0)
                {
                    if (output == null)
                    {
                        return input;
                    }
                    else
                    {
                        if (oldPos < input.Length)
                        {
                            // normal text after last property
                            output.Append(input, oldPos, input.Length - oldPos);
                        }
                        return output.ToString();
                    }
                }

                if (output == null)
                {
                    if (pos == 0)
                        output = new StringBuilder();
                    else
                        output = new StringBuilder(input, 0, pos, pos + 16);
                }
                else
                {
                    if (pos > oldPos)
                    {
                        // normal text between two properties
                        output.Append(input, oldPos, pos - oldPos);
                    }
                }
                int end = input.IndexOf('}', pos + 1);
                if (end < 0)
                {
                    output.Append("${");
                    pos += 2;
                }
                else
                {
                    string property = input.Substring(pos + 2, end - pos - 2);
                    string val = GetValue(property, customTags);
                    if (val == null)
                    {
                        output.Append("${");
                        output.Append(property);
                        output.Append('}');
                    }
                    else
                    {
                        output.Append(val);
                    }
                    pos = end + 1;
                }
            } while (pos < input.Length);
            return output.ToString();
        }

        string GetValue(string propertyName, string[,] customTags)
        {

            // most properties start with res: in lowercase,
            // so we can save 2 string allocations here, in addition to all the jumps
            // All other prefixed properties {prefix:Key} shoulg get handled in the switch below.
            if (propertyName.StartsWith("res:", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    return Parse(ResourceRepository.Strings.Get(propertyName.Substring(4)), customTags);
                }
                catch (ResourceNotFoundException)
                {
                    return null;
                }
            }
            if (propertyName.StartsWith("DATE:", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    return DateTime.Now.ToString(propertyName.Split(':')[1]);
                }
                catch (Exception ex)
                {
                    return ex.Message;
                }
            }
            if (propertyName.Equals("DATE", StringComparison.OrdinalIgnoreCase))
                return DateTime.Today.ToShortDateString();
            if (propertyName.Equals("TIME", StringComparison.OrdinalIgnoreCase))
                return DateTime.Now.ToShortTimeString();
            if (propertyName.Equals("ProductName", StringComparison.OrdinalIgnoreCase))
                return MbsEnvironment.ProductName;
            if (propertyName.Equals("GUID", StringComparison.OrdinalIgnoreCase))
                return Guid.NewGuid().ToString().ToUpperInvariant();

            if (customTags != null)
            {
                for (int j = 0; j < customTags.GetLength(0); ++j)
                {
                    if (propertyName.Equals(customTags[j, 0], StringComparison.OrdinalIgnoreCase))
                    {
                        return customTags[j, 1];
                    }
                }
            }

            if (Properties.ContainsKey(propertyName))
            {
                return Properties[propertyName];
            }

            if (stringTagProviders.ContainsKey(propertyName))
            {
                return stringTagProviders[propertyName].Convert(propertyName);
            }

            int k = propertyName.IndexOf(':');
            if (k <= 0)
                return null;
            string prefix = propertyName.Substring(0, k);
            propertyName = propertyName.Substring(k + 1);
            switch (prefix.ToUpperInvariant())
            {
                case "SDK_PATH":
                    return MbsEnvironment.SDK_Path;
                case "ENV":
                    return Environment.GetEnvironmentVariable(propertyName);
                case "RES":
                    try
                    {
                        return Parse(ResourceRepository.Strings.Get(propertyName), customTags);
                    }
                    catch (ResourceNotFoundException)
                    {
                        return null;
                    }
                case "PROPERTY":
                    return GetProperty(propertyName);
                default:
                    if (propertyObjects.ContainsKey(prefix))
                    {
                        return Get(propertyObjects[prefix], propertyName);
                    }
                    else
                    {
                        return null;
                    }
            }
        }

        /// <summary>
        /// Allow special syntax to retrieve property values:
        /// ${property:PropertyName}
        /// ${property:PropertyName??DefaultValue}
        /// ${property:Mbs.ComponentModel.ContainerName/PropertyName}
        /// ${property:Mbs.ComponentModel.ContainerName/PropertyName??DefaultValue}
        /// A Mbs.ComponentModel.Container is a Properties instance stored in the PropertyService. This is
        /// used by many AddIns to group all their properties into one Mbs.ComponentModel.Container.
        /// </summary>
        string GetProperty(string propertyName)
        {
            string defaultValue = "";
            int pos = propertyName.LastIndexOf("??", StringComparison.Ordinal);
            if (pos >= 0)
            {
                defaultValue = propertyName.Substring(pos + 2);
                propertyName = propertyName.Substring(0, pos);
            }
            pos = propertyName.IndexOf('/');
            if (pos >= 0)
            {
                PropertySet properties = PropertyManager.Instance.Properties.Get(propertyName.Substring(0, pos), new PropertySet());
                propertyName = propertyName.Substring(pos + 1);
                pos = propertyName.IndexOf('/');
                while (pos >= 0)
                {
                    properties = properties.Get(propertyName.Substring(0, pos), new PropertySet());
                    propertyName = propertyName.Substring(pos + 1);
                }
                return properties.Get(propertyName, defaultValue);
            }
            else
            {
                return PropertyManager.Instance.Properties.Get(propertyName, defaultValue);
            }
        }

        string Get(object obj, string name)
        {
            Type type = obj.GetType();
            PropertyInfo prop = type.GetProperty(name);
            if (prop != null)
            {
                return prop.GetValue(obj, null).ToString();
            }
            FieldInfo field = type.GetField(name);
            if (field != null)
            {
                return field.GetValue(obj).ToString();
            }
            return null;
        }
    }
}
